package com.ldl.distributelock.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.ldl.distributelock.lock.DistributedLockClient;
import com.ldl.distributelock.lock.DistributedRedisLock;
import com.ldl.distributelock.mapper.StockMapper;
import com.ldl.distributelock.pojo.Stock;
import com.ldl.distributelock.zk.ZkClient;
import com.ldl.distributelock.zk.ZkDistributedLock;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

@Service
@SuppressWarnings("all")
public class StockService {

    //     private stock stock = new stock() ;
    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private ReentrantLock lock = new ReentrantLock();

    @Autowired
    private DistributedLockClient distributedLockClient;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ZkClient zkClient;

    @Autowired
    private CuratorFramework curatorFramework;

    public void deduct() {
        InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/curator/locks");
        try {
            mutex.acquire();
            try {
                // 1. 获取库存
                String stock = redisTemplate.opsForValue().get("stock");
                // 2. 判断库存是否充足
                if (stock != null && stock.length() != 0) {
                    Integer integer = Integer.valueOf(stock);
                    // 3. 扣减库存
                    if (integer > 0) {
                        redisTemplate.opsForValue().set("stock", String.valueOf(--integer));
                    }
                }
                testZk(mutex);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                mutex.release();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void testZk(InterProcessMutex mutex) throws Exception {
        mutex.acquire();
        System.out.println("测试重入锁。。。");
        mutex.release();
    }

    public void deduct9() {
        ZkDistributedLock lock = zkClient.getLock("lock");
        lock.lock();
        try {
            // 1. 获取库存
            String stock = redisTemplate.opsForValue().get("stock");
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer integer = Integer.valueOf(stock);
                // 3. 扣减库存
                if (integer > 0) {
                    redisTemplate.opsForValue().set("stock", String.valueOf(--integer));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void deduct8() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            // 1. 获取库存
            String stock = redisTemplate.opsForValue().get("stock");
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer integer = Integer.valueOf(stock);
                // 3. 扣减库存
                if (integer > 0) {
                    redisTemplate.opsForValue().set("stock", String.valueOf(--integer));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void deduct7() {
        DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock");
        redisLock.lock();
        try {
            // 1. 获取库存
            String stock = redisTemplate.opsForValue().get("stock");
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer integer = Integer.valueOf(stock);
                // 3. 扣减库存
                if (integer > 0) {
                    redisTemplate.opsForValue().set("stock", String.valueOf(--integer));
                }
            }
//            TimeUnit.SECONDS.sleep(1000);
//            test();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisLock.unlock();
        }
    }

//    public static void main(String[] args) {
////        System.out.println("定时任务的初始时间：" + System.currentTimeMillis());
////        new Timer().schedule(new TimerTask() {
////            @Override
////            public void run() {
////                System.out.println("定时任务的执行时间：" + System.currentTimeMillis());
////            }
////        }, 5000, 10000);
//////        scheduled的线程池只能销毁，无法停止任务
//////        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
//////        System.out.println("定时任务的初始时间：" + System.currentTimeMillis());
//////        scheduledExecutorService.scheduleAtFixedRate(() -> {
//////            System.out.println("定时任务的执行时间：" + System.currentTimeMillis());
//////        }, 5, 10, TimeUnit.SECONDS);
////    }

    public void test() {
        DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock");
        redisLock.lock();
        System.out.println("测试重入锁。。。");
        redisLock.unlock();
    }

    public void deduct6() {
        //为value设置为uuid，保证要删的锁是自己的锁，防误删
        String uuid = UUID.randomUUID().toString();
        // 设置锁  因为递归长时间出错容易出问题，还是有可能栈溢出 改成while循环来解决
        // 且如果获取锁后宕机，则会导致死锁问题  因此需要通过原子性的set设置过期时间
        while (!redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS)) {
            //设置锁失败 循环调用
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 设置锁成功
        try {
            // 1. 获取库存
            String stock = redisTemplate.opsForValue().get("stock");
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer integer = Integer.valueOf(stock);
                // 3. 扣减库存
                if (integer > 0) {
                    redisTemplate.opsForValue().set("stock", String.valueOf(--integer));
                }
            }
        } finally {
            //防误删
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del',KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
            redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
//            不满足原子性删除
//            if (StringUtils.equals(redisTemplate.opsForValue().get("stock"), uuid)) {
//                redisTemplate.delete("lock");
//            }
        }
    }

    public void deduct5() {
        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                // watch
                operations.watch("stock");
                // 1. 查询库存信息
                String stock = operations.opsForValue().get("stock").toString();
                // 2. 判断库存是否充足
                if (stock != null && stock.length() != 0) {
                    // multi
                    operations.multi();
                    Integer integer = Integer.valueOf(stock);
                    // 3. 扣减库存
                    if (integer > 0) {
                        operations.opsForValue().set("stock", String.valueOf(--integer));
                        // exec
                        List exec = operations.exec();
                        if (exec == null || exec.size() == 0) {
                            try {
                                Thread.sleep(40);
                                deduct();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                return null;
            }
        });

    }

    //    @Transactional
    public void deduct4() {
        // 1. 查询库存信息并锁定行
        List<Stock> stocks = stockMapper.selectList(new QueryWrapper<Stock>().eq("product_code", "1001"));
        Stock stock = stocks.get(0);
        // 2. 判断库存是否充足
        if (stock.getCount() >= 1) {
            // 3. 扣减库存
            stock.setCount(stock.getCount() - 1);
            Integer version = stock.getVersion();
            stock.setVersion(version + 1);
            if (stockMapper.update(stock, new UpdateWrapper<Stock>().eq("id", stock.getId()).eq("version", version)) == 0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //4. 如果其他线程已经修改了,即更新失败 那么应该重新去执行扣减操作
                this.deduct();
            }
        }
    }

    @Transactional
    public void deduct3() {
        // 1. 查询库存信息并锁定行
        List<Stock> stocks = stockMapper.queryStock("1001");
        Stock stock = stocks.get(0);
        // 2. 判断库存是否充足
        if (stock.getCount() >= 1) {
            // 3. 扣减库存
            stock.setCount(stock.getCount() - 1);
            stockMapper.updateById(stock);
        }
    }

    public void deduct2() {
        lock.lock();
        try {
            stockMapper.updateStock("1001", 1);
//            Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code", "1001"));
//            if (stock != null && stock.getCount() > 0) {
//                stock.setCount(stock.getCount() - 1);
//                System.out.println("余数：" + stock.getCount());
//                stockMapper.updateById(stock);
//            }
        } finally {
            lock.unlock();
        }
    }

    public void testFairLock(Long id) {
        RLock fairlock = redissonClient.getFairLock("lock");
        fairlock.lock();
        try {
            TimeUnit.SECONDS.sleep(10);
            System.out.println("测试公平锁===========" + id);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            fairlock.unlock();
        }
    }

    public void testReadLock() throws InterruptedException {
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("rwlock");
        rwlock.readLock().lock();
        TimeUnit.SECONDS.sleep(10);
        //一顿读操作
        rwlock.readLock().unlock();
    }

    public void testWriteLock() throws InterruptedException {
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("rwlock");
        rwlock.writeLock().lock();
        TimeUnit.SECONDS.sleep(10);
        //一顿写操作
        rwlock.writeLock().unlock();
    }


    //单机版的限流操作  通过semaphore进行限流
    public static void main(String[] args) throws InterruptedException {
        //将main线程视作班长，所有同学走了之后才能锁门  一个线程等待多个线程
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"准备出门了。。。。");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName()+"出门了。。。。");
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },i+"号同学出门了").start();
        }
        countDownLatch.await();
        System.out.println("班长锁门了");

//        //3个车位
//        Semaphore semaphore = new Semaphore(3);
//        for (int i = 0; i < 6; i++) {
//            new Thread(() -> {
//                try {
//                    semaphore.acquire();
//                    System.out.println(Thread.currentThread().getName()+"抢到了车位");
//                    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
//                    System.out.println(Thread.currentThread().getName()+"停了一会儿开走了");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }, i + "号车").start();
//        }
    }

    //这个信号量需要在redis里删掉再启动 不会自动更新
    public void testSemaphore() {
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        // 限流，设置资源量，即可以执行的线程数
        semaphore.trySetPermits(5);
        try {
            //未获取资源的信号量自旋
            semaphore.acquire();
            redisTemplate.opsForList().rightPush("log","10010"+Thread.currentThread().getName()+"获取了资源，开始处理业务========");
//            System.out.println(Thread.currentThread().getName()+"获取了资源，开始处理业务========");
            TimeUnit.SECONDS.sleep(10+new Random().nextInt(10));
            redisTemplate.opsForList().rightPush("log","10010"+Thread.currentThread().getName()+"处理业务完毕，释放了资源========");
//            System.out.println(Thread.currentThread().getName()+"停了一会儿开走了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
